/* Copyright 2017, 2018, Gabriel Czernikier
 *
 * This file is part of Notehaus.
 * Notehaus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * Notehaus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with Notehaus.  If not, see <http://www.gnu.org/licenses/>.
 */
#define _BSD_SOURCE
#include <endian.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include "composer.h"
#include "lgwseq.h"
#include "types.h"

static LIST_HEAD(type_member_state_head);
static unsigned int type_member_state_size = 0;

LIST_HEAD(type_list_head);
unsigned int type_list_list_size = 0;

void add_type_member(struct type_id_st *type_id) {
  struct type_member_st *type_member = malloc(sizeof(struct type_member_st));
  type_member->type_id = type_id;
  list_add_tail(&type_member->type_member_list,&type_member_state_head);
  type_member_state_size++;
}

struct type_list_st *add_type_list() {
  struct type_list_st *type_list = malloc(sizeof(struct type_list_st));
  type_list->nb_members=type_member_state_size;
  // List head replacement
  list_add(&type_list->type_member_head, &type_member_state_head);
  list_del(&type_member_state_head);
  
  list_add(&type_list->type_list_list, &type_list_head);
  type_list_list_size++;
  // Reset state for next type_list creation
  INIT_LIST_HEAD(&type_member_state_head);
  type_member_state_size=0;
  return type_list;
}

// Comparator that implements a lexicographic order over lists (sequences) by their members.
int otype_list_st_compar(struct otype_list_st *otype_list_1, struct otype_list_st *otype_list_2) {
  struct list_head *tmp_1, *tmp_2,
    *head_1 = &otype_list_1->type_list->type_member_head,
    *head_2 = &otype_list_2->type_list->type_member_head;
  unsigned int idx_1, idx_2;
  list_for_each(tmp_1, head_1)
    break;
  list_for_each(tmp_2, head_2)
    break;
  while(tmp_1!=head_1 && tmp_2!=head_2) {
    if(  ( idx_1 = ((struct type_member_st *)list_entry(tmp_1, struct type_member_st, type_member_list))->type_id->idx ) != ( idx_2 = ((struct type_member_st *)list_entry(tmp_2, struct type_member_st, type_member_list))->type_id->idx )  )
      break;
    list_for_each(tmp_1, tmp_1);   // loop never entered nevertheless initial asignment takes place, resulting in for the pointer to go 1 position ahead
    list_for_each(tmp_2, tmp_2);   // loop never entered nevertheless initial asignment takes place, resulting in for the pointer to go 1 position ahead
  }
  if(tmp_1!=head_1 && tmp_2!=head_2)
    return idx_1-idx_2;
  else if(tmp_1!=head_1)
    return 1;
  else if(tmp_2!=head_2)
    return -1;
  else
    otype_list_1->bool_duplicate = otype_list_2->bool_duplicate = 1;
  return 0;
}

void build_otype_list() {
  otype_list_ary = malloc((type_list_list_size+1/*reserves one more position*/)*sizeof(struct otype_list_st));
  memset(otype_list_ary, 0, (type_list_list_size+1)*sizeof(struct otype_list_st));
  // Prepare an artificial element that when used to compute the relative_offset, the result is correct.
  otype_list_ary->type_list = malloc(sizeof(struct type_list_st));
  otype_list_ary->type_list->nb_members=0;
  otype_list_ary->type_list->relative_offset=0;
  // Hide the temporarly intended array position (care of that upon memory deallocation, if ever performed)
  otype_list_ary++;
  struct list_head *tmp;
  unsigned int i=0;
  list_for_each(tmp, &type_list_head) {
    (otype_list_ary+i)->type_list = (struct type_list_st *)list_entry(tmp, struct type_list_st, type_list_list);
    i++;
  }
  qsort(otype_list_ary, type_list_list_size, sizeof(struct otype_list_st), (int (*)(const void *, const void *))otype_list_st_compar);
  i=1;
  unsigned int major_i = 0, major_idx = 0;
  size_t without_padding_size=0;
  while(major_i!=type_list_list_size) {
    (otype_list_ary+major_i)->type_list->relative_offset = (otype_list_ary+major_i-1)->type_list->relative_offset+without_padding_size+/*padding*/(alignment_ary[NH_TYPE_LIST_IDX]-without_padding_size%alignment_ary[NH_TYPE_LIST_IDX])%alignment_ary[NH_TYPE_LIST_IDX];
    without_padding_size = sizeof(uint32_t/*(struct type_list_st).nb_members*/)+(otype_list_ary+major_i)->type_list->nb_members*sizeof(uint16_t/*(uint16_t) ((type_member_st)list_entry((struct type_list_st).type_member_head)).type_id.idx*/);
    uint32_t packed = htole32((otype_list_ary+major_i)->type_list->nb_members);
    BUFFER_WRITE(buffer[NH_TYPE_LIST_IDX],buf_len[NH_TYPE_LIST_IDX],&packed,buf_offset[NH_TYPE_LIST_IDX],sizeof(uint32_t))
    struct list_head *tmp;
    list_for_each(tmp, &(otype_list_ary+major_i)->type_list->type_member_head) {
      struct type_member_st *tm = (struct type_member_st *)list_entry(tmp,struct type_member_st, type_member_list);
      uint16_t packed = htole16((uint16_t)tm->type_id->idx);
      BUFFER_WRITE(buffer[NH_TYPE_LIST_IDX],buf_len[NH_TYPE_LIST_IDX],&packed,buf_offset[NH_TYPE_LIST_IDX],sizeof(uint16_t))
      static const char FILLPATTERN[] = "\0\0\0\0\0\0"; // Also count on an implicit \0 automatically stored as of C language null terminating strings fashion.
      BUFFER_WRITE(buffer[NH_TYPE_LIST_IDX],buf_len[NH_TYPE_LIST_IDX],FILLPATTERN,buf_offset[NH_TYPE_LIST_IDX],(alignment_ary[NH_TYPE_LIST_IDX]-buf_offset[NH_TYPE_LIST_IDX]%alignment_ary[NH_TYPE_LIST_IDX])%alignment_ary[NH_TYPE_LIST_IDX])
    }
    while(i<type_list_list_size && (otype_list_ary+i)->bool_duplicate && otype_list_st_compar((otype_list_ary+major_i), (otype_list_ary+i))==0) {
      (otype_list_ary+i)->duplicate_major = (otype_list_ary+major_i);
      (otype_list_ary+i)->type_list->relative_offset = (otype_list_ary+major_i)->type_list->relative_offset;
      i++;
    }
    (otype_list_ary+major_i)->bool_duplicate=0;  // Holds with the caluse: bool_duplicate can be set after the _compar function that updates this fields was called for the very last time on this position. 
    if(i!=type_list_list_size)
      (otype_list_ary+major_i)->next_major = (otype_list_ary+i);
    major_i=i++;
    major_idx++;
  }
  // Shrink the buffer and cut away a possible trailling padding (behind last element). Such a trailling padding should be required, if ever, by the alignment of any data going next the end of this one. And thence, such trailling padding should be dealt with by where boundaries between this and other sections are.
  buf_offset[NH_TYPE_LIST_IDX] = (otype_list_ary+major_i-1)->type_list->relative_offset+without_padding_size;
  bounds_move(NH_TYPE_LIST_IDX, (otype_list_ary+major_i-1)->type_list->relative_offset+without_padding_size /*the padding is a property derived from the next item's alignment requirements, which is not applicable to the last item, because of not having, at least inside the collection, a next item */);
  majors_size_ary[NH_TYPE_LIST_IDX] = major_idx;
}

void pack_otype_list() {
  for(struct otype_list_st *otype_list_major = otype_list_ary;otype_list_major!=NULL; otype_list_major=otype_list_major->next_major){
    uint32_t size = htole32(otype_list_major->type_list->nb_members);
    BUFFER_WRITE(buffer[NH_TYPE_LIST_IDX],buf_len[NH_TYPE_LIST_IDX],&size,buf_offset[NH_TYPE_LIST_IDX],sizeof(uint32_t))
    struct list_head *tmp;
    list_for_each(tmp, &otype_list_major->type_list->type_member_head) {
      uint16_t type_idx = htole16(list_entry(tmp, struct type_member_st, type_member_list)->type_id->idx);
      BUFFER_WRITE(buffer[NH_TYPE_LIST_IDX],buf_len[NH_TYPE_LIST_IDX],&type_idx,buf_offset[NH_TYPE_LIST_IDX],sizeof(uint16_t))
    }
  }
}
